{This file is part of CheLive.

CheLive is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Foobar is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Foobar.  If not, see <http://www.gnu.org/licenses/>.}

{Модуль реализует векторы, матрицы и вспомагательные математические функции}
unit CoreMath;

interface

uses
	Math;

const
	{Число меньше этого считается нулем}
	MATH_EPS = 0.00001;
	MATH_PI = 3.1415926535897932384626433832795;
	{Множитель для перевода градусов в радианы}
	MATH_TO_RAD = 0.017453292519943295769236907684886;
	{Множитель для перевода радиан в градусы}
	MATH_TO_DEG = 565.48667764616278292327580899031;

type
	PVec2 = ^TVec2;
	TVec2 = record
		x, y: Single;
	end;
	
	PVec3 = ^TVec3;
	TVec3 = record
		x, y, z: Single;
	end;
	
	PVec4 = ^TVec4;
	TVec4 = record
		x, y, z, w: Single;
	end;
	
	PMat4 = ^TMat4;
	TMat4 = record
		m11, m12, m13, m14: Single;
		m21, m22, m23, m24: Single;
		m31, m32, m33, m34: Single;
		m41, m42, m43, m44: Single;
	end;

function Max(v, v2: Integer): Integer; inline; overload;
function Max(v, v2: Single): Single; inline; overload;
function Min(v, v2: Integer): Integer; inline; overload;
function Min(v, v2: Single): Single; inline; overload;

operator +(v, v2: TVec2) r: TVec2;
operator +(v: TVec2; s: Single) r: TVec2;
operator -(v, v2: TVec2) r: TVec2;
operator -(v: TVec2; s: Single) r: TVec2;
operator *(v, v2: TVec2) r: Single;
operator *(v: TVec2; s: Single) r: TVec2;
operator /(v: TVec2; s: Single) r: TVec2;
function Vec2(x, y: Single): TVec2; inline;
function Vec2Len(v: TVec2): Single; inline;

operator +(v, v2: TVec3) r: TVec3;
operator +(v: TVec3; s: Single) r: TVec3;
operator -(v, v2: TVec3) r: TVec3;
operator -(v: TVec3; s: Single) r: TVec3;
operator *(v, v2: TVec3) r: TVec3;
operator *(v: TVec3; s: Single) r: TVec3;
operator /(v: TVec3; s: Single) r: TVec3;
function Vec3(x, y, z: Single): TVec3; inline; overload;
function Vec3(v: TVec4): TVec3; inline; overload;
function Vec3(webColor: Cardinal): TVec3; inline; overload;
function Vec3Dot(v, v2: TVec3): Single; inline;
function Vec3Len(v: TVec3): Single; inline;
function Vec3Norm(v: TVec3): TVec3; inline;

//Vec4
function Vec4(x, y, z, w: Single): TVec4; inline;
function Vec4(v: TVec3; w: Single = 1): TVec4; inline; overload;
function Vec4(webColor: Cardinal): TVec4; inline; overload;
function Vec4Norm(v: TVec4): TVec4; inline;
function Vec4Mat4(v: TVec4; m: PMat4): TVec4; inline;

//Mat4
function Mat4(m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44: Single): TMat4; inline;
procedure Mat4Ident(m: PMat4); inline;
procedure Mat4Inv(m: PMat4); inline;
procedure Mat4Mult(m, m2: PMat4); inline;
procedure Mat4Trans(m: PMat4; x, y, z: Single); inline;
procedure Mat4RotX(m: PMat4; angle: Single); inline;
procedure Mat4RotY(m: PMat4; angle: Single); inline;
procedure Mat4RotZ(m: PMat4; angle: Single); inline;
procedure Mat4Scale(m: PMat4; x, y, z: Single); inline;
procedure Mat4Persp(m: PMat4; fov, aspect, zNear, zFar: Single); inline;
procedure Mat4Ortho(m: PMat4; w, h, zNear, zFar: Single); inline;

implementation

//Misc
function Max(v, v2: Integer): Integer; begin if v > v2 then Result := v else Result := v2; end;
function Max(v, v2: Single): Single; begin if v > v2 then Result := v else Result := v2; end;
function Min(v, v2: Integer): Integer; begin if v < v2 then Result := v else Result := v2; end;
function Min(v, v2: Single): Single; begin if v < v2 then Result := v else Result := v2; end;

//Vec2
operator +(v, v2: TVec2) r: TVec2;
begin
	r.x := v.x + v2.x;
	r.y := v.y + v2.y;
end;

operator +(v: TVec2; s: Single) r: TVec2;
begin
	r.x := v.x + s;
	r.y := v.y + s;
end;

operator -(v, v2: TVec2) r: TVec2;
begin
	r.x := v.x - v2.x;
	r.y := v.y - v2.y;
end;

operator -(v: TVec2; s: Single) r: TVec2;
begin
	r.x := v.x - s;
	r.y := v.y - s;
end;

operator *(v, v2: TVec2) r: Single;
begin
	Result := Sqrt(v.x * v2.x + v.y * v2.y);
end;

operator *(v: TVec2; s: Single) r: TVec2;
begin
	r.x := v.x * s;
	r.y := v.y * s;
end;

operator /(v: TVec2; s: Single) r: TVec2;
begin
	r.x := v.x / s;
	r.y := v.y / s;
end;

function Vec2(x, y: Single): TVec2;
begin
	Result.x := x;
	Result.y := y;
end;

function Vec2Len(v: TVec2): Single;
begin
	Result := Sqrt(v.x * v.x + v.y * v.y);
end;

//Vec3
operator +(v, v2: TVec3) r: TVec3;
begin
	r.x := v.x + v2.x;
	r.y := v.y + v2.y;
	r.z := v.z + v2.z;
end;

operator +(v: TVec3; s: Single) r: TVec3;
begin
	r.x := v.x + s;
	r.y := v.y + s;
	r.z := v.z + s;
end;

operator -(v, v2: TVec3) r: TVec3;
begin
	r.x := v.x - v2.x;
	r.y := v.y - v2.y;
	r.z := v.z - v2.z;
end;

operator -(v: TVec3; s: Single) r: TVec3;
begin
	r.x := v.x - s;
	r.y := v.y - s;
	r.z := v.z - s;
end;

operator *(v, v2: TVec3) r: TVec3;
begin
	r.x := v.y * v2.z - v.z * v2.y;
	r.y := v.z * v2.x - v.x * v2.z;
	r.z := v.x * v2.y - v.y * v2.x;
end;

operator *(v: TVec3; s: Single) r: TVec3;
begin
	r.x := v.x * s;
	r.y := v.y * s;
	r.z := v.z * s;
end;

operator /(v: TVec3; s: Single) r: TVec3;
begin
	r.x := v.x / s;
	r.y := v.y / s;
	r.z := v.z / s;
end;

function Vec3(x, y, z: Single): TVec3;
begin
	Result.x := x;
	Result.y := y;
	Result.z := z;
end;

function Vec3(v: TVec4): TVec3;
begin
	Result.x := v.x;
	Result.y := v.y;
	Result.z := v.z;
end;

function Vec3(webColor: Cardinal): TVec3;
begin
	Result.x := ((webColor and $FF0000) shr 16) / 255;
	Result.y := ((webColor and $FF00) shr 8) / 255;
	Result.z := (webColor and $FF) / 255;
end;

function Vec3Dot(v, v2: TVec3): Single;
begin
	Result := Sqrt(v.x * v2.x + v.y * v2.y + v.z * v2.z);
end;

function Vec3Len(v: TVec3): Single;
begin
	Result := Sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
end;

function Vec3Norm(v: TVec3): TVec3;
var
	len: Single;
begin
	len := Sqrt(v.x * v.x + v.y * v.y + v.z * v.z);

	if len < MATH_EPS then
	begin
		Result := v;
	end else
	begin
		Result := Vec3(v.x / len, v.y / len, v.z / len);
	end;
end;

function Vec4Mat4(v: TVec4; m: PMat4): TVec4;
begin
	Result.x := v.x * m^.m11 + v.y * m^.m21 + v.z * m^.m31 + v.w * m^.m41;
	Result.y := v.x * m^.m12 + v.y * m^.m22 + v.z * m^.m32 + v.w * m^.m42;
	Result.z := v.x * m^.m13 + v.y * m^.m23 + v.z * m^.m33 + v.w * m^.m43;
	Result.w := v.x * m^.m14 + v.y * m^.m24 + v.z * m^.m34 + v.w * m^.m44;
end;

//Vec4
function Vec4(x, y, z, w: Single): TVec4;
begin
	Result.x := x;
	Result.y := y;
	Result.z := z;
	Result.w := w;
end;

function Vec4(v: TVec3; w: Single = 1): TVec4;
begin
	Result.x := v.x;
	Result.y := v.y;
	Result.z := v.z;
	Result.w := w;
end;

function Vec4(webColor: Cardinal): TVec4;
begin
	Result.x := ((webColor and $FF0000) shr 16) / 255;
	Result.y := ((webColor and $FF00) shr 8) / 255;
	Result.z := (webColor and $FF) / 255;
	Result.w := ((webColor and $FF000000) shr 24) / 255;
end;

function Vec4Norm(v: TVec4): TVec4;
var
	len: Single;
begin
	len := Sqrt(v.x * v.x + v.y * v.y + v.z * v.z);

	if len < MATH_EPS then
	begin
		Result := v;
	end else
	begin
		Result := Vec4(v.x / len, v.y / len, v.z / len, v.w / len);
	end;
end;

//Mat4
function Mat4(m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44: Single): TMat4;
begin
	Result.m11 := m11;
	Result.m12 := m12;
	Result.m13 := m13;
	Result.m14 := m14;

	Result.m21 := m21;
	Result.m22 := m22;
	Result.m23 := m23;
	Result.m24 := m24;

	Result.m31 := m31;
	Result.m32 := m32;
	Result.m33 := m33;
	Result.m34 := m34;

	Result.m41 := m41;
	Result.m42 := m42;
	Result.m43 := m43;
	Result.m44 := m44;
end;

procedure Mat4Ident(m: PMat4);
begin
  m^ := Mat4(
    1, 0, 0, 0,
    0, 1, 0, 0,
    0, 0, 1, 0,
    0, 0, 0, 1
  );
end;

procedure Mat4Inv(m: PMat4);
var
	t: TMat4;
begin
	Move(m^, t, SizeOf(t));
	
	m^.m11 := t.m11;
	m^.m12 := t.m21;
	m^.m13 := t.m31;
	m^.m14 := 0;
	
	m^.m21 := t.m12;
	m^.m22 := t.m22;
	m^.m23 := t.m32;
	m^.m24 := 0;
	
	m^.m31 := t.m13;
	m^.m32 := t.m23;
	m^.m33 := t.m33;
	m^.m34 := 0;
	
	m^.m41 := -(t.m41 * t.m11) - (t.m42 * t.m12) - (t.m43 * t.m13);
	m^.m42 := -(t.m41 * t.m21) - (t.m42 * t.m22) - (t.m43 * t.m23);
	m^.m43 := -(t.m41 * t.m31) - (t.m42 * t.m32) - (t.m43 * t.m33);
	m^.m44 := 1;
end;

procedure Mat4Mult(m, m2: PMat4);
var
	t: TMat4;
begin
	Move(m^, t, sizeof(t));

	m^.m11 := t.m11 * m2^.m11 + t.m12 * m2^.m21 + t.m13 * m2^.m31 + t.m14 * m2^.m41;
	m^.m12 := t.m11 * m2^.m12 + t.m12 * m2^.m22 + t.m13 * m2^.m32 + t.m14 * m2^.m42;
	m^.m13 := t.m11 * m2^.m13 + t.m12 * m2^.m23 + t.m13 * m2^.m33 + t.m14 * m2^.m43;
	m^.m14 := t.m11 * m2^.m14 + t.m12 * m2^.m24 + t.m13 * m2^.m34 + t.m14 * m2^.m44;

	m^.m21 := t.m21 * m2^.m11 + t.m22 * m2^.m21 + t.m23 * m2^.m31 + t.m24 * m2^.m41;
	m^.m22 := t.m21 * m2^.m12 + t.m22 * m2^.m22 + t.m23 * m2^.m32 + t.m24 * m2^.m42;
	m^.m23 := t.m21 * m2^.m13 + t.m22 * m2^.m23 + t.m23 * m2^.m33 + t.m24 * m2^.m43;
	m^.m24 := t.m21 * m2^.m14 + t.m22 * m2^.m24 + t.m23 * m2^.m34 + t.m24 * m2^.m44;

	m^.m31 := t.m31 * m2^.m11 + t.m32 * m2^.m21 + t.m33 * m2^.m31 + t.m34 * m2^.m41;
	m^.m32 := t.m31 * m2^.m12 + t.m32 * m2^.m22 + t.m33 * m2^.m32 + t.m34 * m2^.m42;
	m^.m33 := t.m31 * m2^.m13 + t.m32 * m2^.m23 + t.m33 * m2^.m33 + t.m34 * m2^.m43;
	m^.m34 := t.m31 * m2^.m14 + t.m32 * m2^.m24 + t.m33 * m2^.m34 + t.m34 * m2^.m44;

	m^.m41 := t.m41 * m2^.m11 + t.m42 * m2^.m21 + t.m43 * m2^.m31 + t.m44 * m2^.m41;
	m^.m42 := t.m41 * m2^.m12 + t.m42 * m2^.m22 + t.m43 * m2^.m32 + t.m44 * m2^.m42;
	m^.m43 := t.m41 * m2^.m13 + t.m42 * m2^.m23 + t.m43 * m2^.m33 + t.m44 * m2^.m43;
	m^.m44 := t.m41 * m2^.m14 + t.m42 * m2^.m24 + t.m43 * m2^.m34 + t.m44 * m2^.m44;
end;

procedure Mat4Trans(m: PMat4; x, y, z: Single);
begin
	m^ := Mat4(
		1, 0, 0, 0,
		0, 1, 0, 0,
		0, 0, 1, 0,
		x, y, z, 1
	);
end;

procedure Mat4RotX(m: PMat4; angle: Single);
var
	s, c: Single;
begin
	s := Sin(angle * MATH_TO_RAD);
	c := Cos(angle * MATH_TO_RAD);

	m^ := Mat4(
		1, 0, 0, 0,
		0, c, s, 0,
		0, -s, c, 0,
		0, 0, 0, 1
	);
end;

procedure Mat4RotY(m: PMat4; angle: Single);
var
	s, c: Single;
begin
	s := Sin(angle * MATH_TO_RAD);
	c := Cos(angle * MATH_TO_RAD);

	m^ := Mat4(
		c, 0, -s, 0,
		0, 1, 0, 0,
		s, 0, c, 0,
		0, 0, 0, 1
	);
end;

procedure Mat4RotZ(m: PMat4; angle: Single);
var
	s, c: Single;
begin
	s := Sin(angle * MATH_TO_RAD);
	c := Cos(angle * MATH_TO_RAD);

	m^ := Mat4(
		c, s, 0, 0,
		-s, c, 0, 0,
		0, 0, 1, 0,
		0, 0, 0, 1
	);
end;

procedure Mat4Scale(m: PMat4; x, y, z: Single);
begin
	m^ := Mat4(
		x, 0, 0, 0,
		0, y, 0, 0,
		0, 0, z, 0,
		0, 0, 0, 1
	);
end;

procedure Mat4Persp(m: PMat4; fov, aspect, zNear, zFar: Single);
var
	f: Single;
begin
	f := 1 / Tan(fov / 2 * MATH_TO_RAD);

	m^ := Mat4(
		f / aspect, 0, 0, 0,
		0, f, 0, 0,
		0, 0, (zFar + zNear) / (zNear - zFar), -1,
		0, 0, 2 * zFar * zNear / (zNear - zFar), 1
	);
end;

procedure Mat4Ortho(m: PMat4; w, h, zNear, zFar: Single);
begin
	m^ := Mat4(
		2 / w, 0, 0, 0,
		0, -2 / h, 0, 0,
		0, 0, -2 / (zFar - zNear), 0,
		-1, 1, -(zFar + zNear) / (zFar - zNear), 1
	);
end;

end.
